// ArgumentParser.cpp: implementation of the CArgumentParser class.
//
//////////////////////////////////////////////////////////////////////
/*
	---THE FRAMEWORK-----

	The options with their values will be stored as in an array of strings.
	The data-structures when combined will function like a very simple hash-table.
	If the ith position of m_strOptions holds the option,
		the ith position of m_straValue will hold the value supplied for the option, if any.
		the ith position of m_straHelp will hold the help for the option, if any
		the ith position of m_straExpandedOption will hold the expansion of the option

	This scheme allows for an efficient retrieval of argument-values.

		/////////////////////////
		//MEMORY-VIEW////////////
		/////////////////////////
Tables	Options			Values						Help		..
		[Opt]	[][][][][][][]..........[\0]	[][][][][][][]..........[\0]
		[Opt]	[][][][][][][]..........[\0]		..
		[Opt]	[][][][][][][]..........[\0]		..
			.	.	.	.	.	.
			.	.	.	.	.	.
			.	.	.	.	.	.
		[Opt]	[][][][][][][]..........[\0]

	This will be filled in appropriately while parsing the supplied options.

	For example, if we are supplied with "-c -d foo" in operate
	the contents of the memory will look like
		[c]		[0]
		[d]		[f][o][o][\0]

*/			////////////////////////
	
#include "ArgumentParser.h"

#include"StringFunctions.h"
#include"Helper.h"

//Instantiate static members
const char  CArgumentParser::cArgumentSpecifier = ':';
const char  CArgumentParser::cOptionPrefix = '-';

/////////////////////////
//Protected functions ///
/////////////////////////
bool CArgumentParser::isValueEssentialForOption(char cOption)
{
	//Refer to the original syntax specification
	int n=strlen(m_strSyntax);
	int i;
	
	for(i=0;i<n;i++){
		if(m_strSyntax[i]==cOption)
			break;
	}

	if(m_strSyntax[i+1]==cArgumentSpecifier)
		return true;
	return false;
}

int CArgumentParser::indexOfOption(char c)				//Is option there in current syntax-string?
{
	//Refers to the position in the "key" table
	int n=strlen(m_strOption);
	for(int i=0;i<n;i++)
		if(m_strOption[i]==c)
			return i;
	return -1;
}

void CArgumentParser::invalidateOption(char c)			//Remove option from syntax
{
	//Modifies the working copy of syntax, removes the option because we have already used it
	int n=strlen(m_strSyntaxWorkingCopy);
	int i;
	
	for(i=0;i<n;i++)
		if(m_strSyntaxWorkingCopy[i]==c)
			break;
	
	CStringFunctions::deleteAt(i, m_strSyntaxWorkingCopy);
}


/////////////////////////
//Public functions //////
/////////////////////////
int CArgumentParser::parse(int& optc, char **argv)
{
	int validOption=-1,len;
	int lastWord=0;
	
	//Analyze word by word
	for(int i=1;i<optc;++i){
			
		if(m_bWasThereMoreToFill){
				fillValueForOption(m_strOption[validOption],argv[i]);
				lastWord = i;
				m_bWasThereMoreToFill=false;
				m_bWasParseSuccess=true;
				continue;
		}

		m_bIsInOptionMode=false;
		len=strlen(argv[i]);
		for(int j=0;j<len;++j){

			if(!m_bIsInOptionMode)
			{
				if(argv[i][j]==cOptionPrefix)
				{
					//Found the start of an option
					m_bIsInOptionMode=true;
					lastWord = i;
					continue;
				}
				else
				{
					break;	//Ignore the current word as a non-option parameter
					//Bug: Please correct. Unless it is at the end, we cannot accept
				}
				
			}

			validOption=indexOfOption(argv[i][j]);
			if(validOption<0){
					m_bWasParseSuccess=false;
					return lastWord;
			}
			m_abSupplied[validOption]=true; // Mark the option as "supplied"
					
			if(isValueEssentialForOption(m_strOption[validOption])){
					if(argv[i][j+1]){
						fillValueForOption(m_strOption[validOption],argv[i]+j+1);
						m_bIsInOptionMode=false;
						m_bWasParseSuccess=true;
					}
					else{
						m_bWasThereMoreToFill=true;//We have to wait for parameter
						m_bWasParseSuccess=false;//Only if we get the value can we mark success
					}
					break;
			}
		}//j-loop
	}//i-loop
	
	m_bWasParseSuccess=true;
	return lastWord;
}


CArgumentParser::CArgumentParser(char* strSyntax)
{
	int i;
	int nLength = strlen(strSyntax);

	m_bWasParseSuccess = true;
	m_bIsInOptionMode = false;
	m_bWasThereMoreToFill=false;

	m_strSyntax=new char[nLength+1];
	strcpy(m_strSyntax,strSyntax);	//Copy the syntax-specification
	
	//Make working-copy
	m_strSyntaxWorkingCopy= new char[nLength+1];
	strcpy(m_strSyntaxWorkingCopy, strSyntax);

	//Strip the working-copy down to just options
	//Just making sure that no duplicates are there etc
	CStringFunctions::makeLetterSet(m_strSyntaxWorkingCopy);
	CStringFunctions::removeLetter(' ',m_strSyntaxWorkingCopy);	
	CStringFunctions::removeLetter(cArgumentSpecifier,m_strSyntaxWorkingCopy);	

	m_nOptions=	strlen(m_strSyntaxWorkingCopy);
	m_strOption=new char [m_nOptions+1];
	strcpy(m_strOption,m_strSyntaxWorkingCopy);

	//Initialize value area
	m_straValue = new char* [m_nOptions];
	for(i=0;i<m_nOptions;i++)
		m_straValue[i] = NULL;
	
	m_abSupplied = new bool[m_nOptions];
	for(i=0;i<m_nOptions;i++)
		m_abSupplied[i] = false;
	
	m_Helper=new CHelper(m_nOptions);
}

CArgumentParser::~CArgumentParser()
{
	int i;

	if(m_strSyntax)	
		delete[] m_strSyntax;
	if(m_strSyntaxWorkingCopy) 
		delete[] m_strSyntaxWorkingCopy;
	if(m_strOption) 
		delete[] m_strOption;

	for(i=0;i<m_nOptions;i++)
		if(m_straValue[i]) 
			delete[] m_straValue[i];
	
	if (m_straValue) 
		delete[] m_straValue;

	if(m_Helper)
		delete m_Helper;

	if(m_abSupplied)
		delete m_abSupplied;

}


bool CArgumentParser::wasOptionSupplied(char cOption)
{
	int index=indexOfOption(cOption);
	if(index<0){
		throw "No such option.";
	}
	
	if(m_abSupplied[index])
		return true;
	return false;
}

char* CArgumentParser::getField(char cOption)
{
	int index=indexOfOption(cOption);
	if(index<0){
		throw "No such option.";
	}
	if(m_straValue[index]){
		return m_straValue[index];
	}
	return NULL;
}

char* CArgumentParser::getField(int nPos, char& cOption)
{
	if(nPos>=m_nOptions){
		throw "Error getting field nPos";
	}
	cOption=m_strOption[nPos];
	return m_straValue[nPos];
}

bool CArgumentParser::wasParseSuccessful()
{
	return m_bWasParseSuccess;
}

void CArgumentParser::fillValueForOption(char cOption,char* strValue)
{
	if(!fillArrayForOption(cOption,m_straValue,strValue)){
		throw "Error filling array for option.";
	}
}

void CArgumentParser::fillHelpForOption(char cOption, char* strValue)
{
	int index=indexOfOption(cOption);
	if(index<0){
		throw "No such option.";
	}
	m_Helper->fillHelpForOption(index,cOption,strValue);
}

bool CArgumentParser::fillArrayForOption(char cOption,char**paArray,char*strValue)
{

	int index=indexOfOption(cOption);

	if(index<0){
		throw "No such option";
	}
	if(!paArray){
		throw "No such array";
	}
	
	int length=strlen(strValue);
	if(paArray[index]) delete[] paArray[index];
	paArray[index]=new char [length+1];
	strcpy(paArray[index],strValue);
	
	return true;
}

bool CArgumentParser::isValidOption(char cOption)
{
	if(indexOfOption(cOption)<0){
		return false;
	}
	return true;
}
